/********************************************************
 * Kernels to be optimized for the CS:APP Performance Lab
 ********************************************************/

#include <stdio.h>
#include <stdlib.h>
#include "defs.h"

/* 
 * ECE454 Students: 
 * Please fill in the following team struct 
 */
team_t team = {
    "HanSolo",              /* Team name */

    "Mustafa Abbas",     /* First member full name */
    "abbasmus@ecf.utoronto.ca",  /* First member email address */

    "",                   /* Second member full name (leave blank if none) */
    ""                    /* Second member email addr (leave blank if none) */
};

/***************
 * ROTATE KERNEL
 ***************/

/******************************************************
 * Your different versions of the rotate kernel go here
 ******************************************************/

/* 
 * naive_rotate - The naive baseline version of rotate 
 */
char naive_rotate_descr[] = "naive_rotate: Naive baseline implementation";
void naive_rotate(int dim, pixel *src, pixel *dst) 
{
    int i, j;

    for (i = 0; i < dim; i++)
	for (j = 0; j < dim; j++)
	    dst[RIDX(dim-1-j, i, dim)] = src[RIDX(i, j, dim)];
}

// putting function headers for the various attempts
void attempt_one(int dim, pixel *src, pixel *dst);
void attempt_two(int dim, pixel *src, pixel *dst);
void attempt_three(int dim, pixel *src, pixel *dst);
void attempt_four(int dim, pixel *src, pixel *dst);
void attempt_five(int dim, pixel *src, pixel *dst);
void attempt_six(int dim, pixel *src, pixel *dst);
void attempt_seven(int dim, pixel *src, pixel *dst);

/*
 * ECE 454 Students: Write your rotate functions here:
 */ 

/* 
 * rotate - Your current working version of rotate
 * IMPORTANT: This is the version you will be graded on
 */
char rotate_descr[] = "rotate: Current working version";
void rotate(int dim, pixel *src, pixel *dst) {
    //optimizing for each of the test sizes
    if (dim <= 64) {
        attempt_one(dim, src, dst);
    }
    else {
        attempt_seven(dim, src, dst);
    }
}

char rotate_one_descr[] = "attempt_one: switching the naive implementation rows and columns";
void attempt_one(int dim, pixel *src, pixel *dst) 
{
    int i, j;

	for (j = 0; j < dim; j++)
    for (i = 0; i < dim; i++)
	    dst[RIDX(dim-1-j, i, dim)] = src[RIDX(i, j, dim)];
}

char rotate_two_descr[] = "attempt_two: basic tiling by 32";
void attempt_two(int dim, pixel *src, pixel *dst) {
    int i, j;

    int tileSize = 32;
    int iTile, jTile;

    for (j = 0; j < dim; j += tileSize) {
        for (i = 0; i < dim; i += tileSize) {

            // tiling here.
            for (jTile = j; jTile < j+tileSize; jTile++) {
                for (iTile = i; iTile < i+tileSize; iTile++) {
                    dst[RIDX(dim-1-jTile, iTile, dim)] = src[RIDX(iTile, jTile, dim)];
                }
            }
        }
    }
}

char rotate_three_descr[] = "attempt_three: unrolling the outer loop by 4 and inner loop by 2";
void attempt_three(int dim, pixel *src, pixel *dst) {
    int i, j;

    int tileSize = 32;
    int iTile, jTile;

    for (j = 0; j < dim; j += tileSize) {
        for (i = 0; i < dim; i += tileSize) {

            // tiling here.
            for (jTile = j; jTile < j+tileSize; jTile+=4) {
                for (iTile = i; iTile < i+tileSize; iTile+=2) {
                    dst[RIDX(dim-1-(jTile+0), iTile, dim)] = src[RIDX(iTile, (jTile+0), dim)];
                    dst[RIDX(dim-1-(jTile+0), iTile+1, dim)] = src[RIDX(iTile+1, (jTile+0), dim)];
                }
                for (iTile = i; iTile < i+tileSize; iTile+=2) {
                    dst[RIDX(dim-1-(jTile+1), iTile, dim)] = src[RIDX(iTile, (jTile+1), dim)];
                    dst[RIDX(dim-1-(jTile+1), iTile+1, dim)] = src[RIDX(iTile+1, (jTile+1), dim)];
                }
                for (iTile = i; iTile < i+tileSize; iTile+=2) {
                    dst[RIDX(dim-1-(jTile+2), iTile, dim)] = src[RIDX(iTile, (jTile+2), dim)];
                    dst[RIDX(dim-1-(jTile+2), iTile+1, dim)] = src[RIDX(iTile+1, (jTile+2), dim)];
                }
                for (iTile = i; iTile < i+tileSize; iTile+=2) {
                    dst[RIDX(dim-1-(jTile+3), iTile, dim)] = src[RIDX(iTile, (jTile+3), dim)];
                    dst[RIDX(dim-1-(jTile+3), iTile+1, dim)] = src[RIDX(iTile+1, (jTile+3), dim)];
                }
            }
        }
    }
}

char rotate_four_descr[] = "attempt_four: unrolling the outer loop by 4 only";
void attempt_four(int dim, pixel *src, pixel *dst) {
    int i, j;

    int tileSize = 32;
    int iTile, jTile;

    for (j = 0; j < dim; j += tileSize) {
        for (i = 0; i < dim; i += tileSize) {

            // tiling here.
            for (jTile = j; jTile < j+tileSize; jTile+=4) {
                for (iTile = i; iTile < i+tileSize; iTile++) {
                    dst[RIDX(dim-1-(jTile+0), iTile, dim)] = src[RIDX(iTile, (jTile+0), dim)];
                }
                for (iTile = i; iTile < i+tileSize; iTile++) {
                    dst[RIDX(dim-1-(jTile+1), iTile, dim)] = src[RIDX(iTile, (jTile+1), dim)];
                }
                for (iTile = i; iTile < i+tileSize; iTile++) {
                    dst[RIDX(dim-1-(jTile+2), iTile, dim)] = src[RIDX(iTile, (jTile+2), dim)];
                }
                for (iTile = i; iTile < i+tileSize; iTile++) {
                    dst[RIDX(dim-1-(jTile+3), iTile, dim)] = src[RIDX(iTile, (jTile+3), dim)];
                }
            }
        }
    }
}

char rotate_five_descr[] = "attempt_five: playing around with a different tile size for i and j. i-32, j-32";
void attempt_five(int dim, pixel *src, pixel *dst) {
    int i, j;

    int iTileSize = 32;
    int jTileSize = 32;
    int iTile, jTile;

    for (j = 0; j < dim; j += jTileSize) {
        for (i = 0; i < dim; i += iTileSize) {

            // tiling here.
            for (jTile = j; jTile < j+jTileSize; jTile+=4) {
                for (iTile = i; iTile < i+iTileSize; iTile++) {
                    dst[RIDX(dim-1-(jTile+0), iTile, dim)] = src[RIDX(iTile, (jTile+0), dim)];
                }
                for (iTile = i; iTile < i+iTileSize; iTile++) {
                    dst[RIDX(dim-1-(jTile+1), iTile, dim)] = src[RIDX(iTile, (jTile+1), dim)];
                }
                for (iTile = i; iTile < i+iTileSize; iTile++) {
                    dst[RIDX(dim-1-(jTile+2), iTile, dim)] = src[RIDX(iTile, (jTile+2), dim)];
                }
                for (iTile = i; iTile < i+iTileSize; iTile++) {
                    dst[RIDX(dim-1-(jTile+3), iTile, dim)] = src[RIDX(iTile, (jTile+3), dim)];
                }
            }
        }
    }
}

char rotate_six_descr[] = "attempt_six: playing around with a different tile size for i and j. i-8, j-32";
void attempt_six(int dim, pixel *src, pixel *dst) {
    int i, j;

    int iTileSize = 8;
    int jTileSize = 32;
    int iTile, jTile;

    for (j = 0; j < dim; j += jTileSize) {
        for (i = 0; i < dim; i += iTileSize) {

            // tiling here.
            for (jTile = j; jTile < j+jTileSize; jTile+=4) {
                for (iTile = i; iTile < i+iTileSize; iTile++) {
                    dst[RIDX(dim-1-(jTile+0), iTile, dim)] = src[RIDX(iTile, (jTile+0), dim)];
                }
                for (iTile = i; iTile < i+iTileSize; iTile++) {
                    dst[RIDX(dim-1-(jTile+1), iTile, dim)] = src[RIDX(iTile, (jTile+1), dim)];
                }
                for (iTile = i; iTile < i+iTileSize; iTile++) {
                    dst[RIDX(dim-1-(jTile+2), iTile, dim)] = src[RIDX(iTile, (jTile+2), dim)];
                }
                for (iTile = i; iTile < i+iTileSize; iTile++) {
                    dst[RIDX(dim-1-(jTile+3), iTile, dim)] = src[RIDX(iTile, (jTile+3), dim)];
                }
            }
        }
    }
}

char rotate_seven_descr[] = "attempt_seven: tailoring different tile sizes for dim and combining attempts 5 and 6";
void attempt_seven(int dim, pixel *src, pixel *dst) {
    int i, j;

    int iTileSize, jTileSize, iTile, jTile;

    if (dim < 1024) {
        iTileSize = 32;
        jTileSize = 32;
    }
    else {
        iTileSize = 8;
        jTileSize = 32;
    }

    for (j = 0; j < dim; j += jTileSize) {
        for (i = 0; i < dim; i += iTileSize) {

            // tiling here.
            for (jTile = j; jTile < j+jTileSize; jTile+=4) {
                for (iTile = i; iTile < i+iTileSize; iTile++) {
                    dst[RIDX(dim-1-(jTile+0), iTile, dim)] = src[RIDX(iTile, (jTile+0), dim)];
                }
                for (iTile = i; iTile < i+iTileSize; iTile++) {
                    dst[RIDX(dim-1-(jTile+1), iTile, dim)] = src[RIDX(iTile, (jTile+1), dim)];
                }
                for (iTile = i; iTile < i+iTileSize; iTile++) {
                    dst[RIDX(dim-1-(jTile+2), iTile, dim)] = src[RIDX(iTile, (jTile+2), dim)];
                }
                for (iTile = i; iTile < i+iTileSize; iTile++) {
                    dst[RIDX(dim-1-(jTile+3), iTile, dim)] = src[RIDX(iTile, (jTile+3), dim)];
                }
            }
        }
    }
}

/*********************************************************************
 * register_rotate_functions - Register all of your different versions
 *     of the rotate kernel with the driver by calling the
 *     add_rotate_function() for each test function. When you run the
 *     driver program, it will test and report the performance of each
 *     registered test function.  
 *********************************************************************/

void register_rotate_functions() 
{
    add_rotate_function(&naive_rotate, naive_rotate_descr);   
    add_rotate_function(&rotate, rotate_descr);

    //add_rotate_function(&attempt_one, rotate_one_descr);   
    //add_rotate_function(&attempt_two, rotate_two_descr);   
    //add_rotate_function(&attempt_three, rotate_three_descr);   
    //add_rotate_function(&attempt_four, rotate_four_descr);   
    //add_rotate_function(&attempt_five, rotate_five_descr);   
    //add_rotate_function(&attempt_six, rotate_six_descr);   
    //add_rotate_function(&attempt_seven, rotate_seven_descr);   
    //add_rotate_function(&attempt_eight, rotate_eight_descr);   
    //add_rotate_function(&attempt_nine, rotate_nine_descr);   
    //add_rotate_function(&attempt_ten, rotate_ten_descr);   
    //add_rotate_function(&attempt_eleven, rotate_eleven_descr);   

    /* ... Register additional rotate functions here */
}

